package com.sunxiansheng.inteceptor;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.defaults.DefaultSqlSession.StrictMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Matcher;

/**
 * SQL优化器：显示完整的SQL
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
public class SqlBeautyInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(SqlBeautyInterceptor.class);

    private static final Set<Class<?>> PRIMITIVE_WRAPPER_CLASSES = new HashSet<>(Arrays.asList(
            Byte.class, Short.class, Integer.class, Long.class, Double.class, Float.class, Character.class, Boolean.class));

    private static final String ANSI_RESET = "\u001B[0m";
    private static final String ANSI_YELLOW = "\u001B[33m";
    private static final String ANSI_GREEN = "\u001B[32m";
    private static final String ANSI_BLUE = "\u001B[34m";
    private static final String ANSI_RED = "\u001B[31m";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        String mapperMethod = mappedStatement.getId();
        long startTime = System.currentTimeMillis();
        String threadName = Thread.currentThread().getName();
        long threadId = Thread.currentThread().getId();
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        try {
            return invocation.proceed();
        } catch (Throwable throwable) {
            logger.error("SQL execution error: ", throwable);
            throw throwable;
        } finally {
            long endTime = System.currentTimeMillis();
            long sqlCost = endTime - startTime;
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = boundSql.getSql();
            Object parameterObject = boundSql.getParameterObject();
            List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();
            String formattedSql = sql;
            try {
                formattedSql = formatSql(sql, parameterObject, parameterMappingList);
                formattedSql = beautifySql(formattedSql);
            } catch (Exception e) {
                logger.error("Error formatting SQL: ", e);
                formattedSql = formatSql(sql, parameterObject, parameterMappingList); // 如果格式化失败，返回未格式化但填充了数据的SQL
            }
            logger.info("\n========================\n线程信息：{}{} (ID: {}){}\n时间戳：{}{}{}\n堆栈信息：{}\nMapper方法：{}{}{}\nSQL:\n{}{}{}\n执行耗时: {}{}{} ms\n========================",
                    ANSI_RED, threadName, threadId, ANSI_RESET,
                    ANSI_GREEN, timestamp, ANSI_RESET,
                    Arrays.toString(Thread.currentThread().getStackTrace()),
                    ANSI_BLUE, mapperMethod, ANSI_RESET, ANSI_YELLOW, formattedSql, ANSI_RESET, ANSI_GREEN, sqlCost, ANSI_RESET);
        }
    }

    private String getMapperMethod(StatementHandler statementHandler) {
        try {
            Field mappedStatementField = statementHandler.getClass().getDeclaredField("mappedStatement");
            mappedStatementField.setAccessible(true);
            MappedStatement mappedStatement = (MappedStatement) mappedStatementField.get(statementHandler);
            return mappedStatement.getId();
        } catch (Exception e) {
            logger.error("Error getting mapper method: ", e);
            return "Unknown method";
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

    private String formatSql(String sql, Object parameterObject, List<ParameterMapping> parameterMappingList) {
        if (sql == null || sql.trim().isEmpty()) {
            return "";
        }
        if (parameterObject == null || parameterMappingList == null || parameterMappingList.isEmpty()) {
            return sql;
        }
        String sqlWithoutReplacePlaceholder = sql;
        try {
            if (isStrictMap(parameterObject.getClass())) {
                StrictMap<?> strictMap = (StrictMap<?>) parameterObject;
                if (isList(strictMap.get("list").getClass())) {
                    sql = handleListParameter(sql, (List<?>) strictMap.get("list"));
                }
            } else if (isMap(parameterObject.getClass())) {
                sql = handleMapParameter(sql, (Map<?, ?>) parameterObject, parameterMappingList);
            } else {
                sql = handleCommonParameter(sql, parameterMappingList, parameterObject);
            }
        } catch (Exception e) {
            logger.error("Error formatting SQL: ", e);
            return sqlWithoutReplacePlaceholder; // 返回未格式化但填充了数据的SQL
        }
        return sql;
    }

    private String handleCommonParameter(String sql, List<ParameterMapping> parameterMappingList, Object parameterObject) throws Exception {
        Class<?> parameterObjectClass = parameterObject.getClass();
        List<Field> allFields = new ArrayList<>();
        while (parameterObjectClass != null) {
            allFields.addAll(Arrays.asList(parameterObjectClass.getDeclaredFields()));
            parameterObjectClass = parameterObjectClass.getSuperclass();
        }
        for (ParameterMapping parameterMapping : parameterMappingList) {
            String propertyValue = null;
            String propertyName = parameterMapping.getProperty();
            Field field = allFields.stream().filter(f -> f.getName().equals(propertyName)).findFirst().orElse(null);
            if (field != null) {
                field.setAccessible(true);
                Object value = field.get(parameterObject);
                propertyValue = formatParameterValue(value);
                logger.debug("Parameter name: {}, value: {}, type: {}", propertyName, propertyValue, value != null ? value.getClass().getSimpleName() : "null");
            } else if (isPrimitiveOrPrimitiveWrapper(parameterObject.getClass())) {
                propertyValue = parameterObject.toString();
                logger.debug("Primitive parameter: {}, value: {}", propertyName, propertyValue);
            }
            if (propertyValue != null) {
                sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(propertyValue));
            }
        }
        return sql;
    }

    private String formatParameterValue(Object value) {
        if (value == null) {
            return "null";
        } else if (value instanceof String) {
            return "\"" + value + "\"";
        } else if (value instanceof LocalDateTime) {
            return "\"" + value.toString() + "\"";
        } else if (value instanceof Date) {
            return "\"" + new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value) + "\"";
        } else if (value instanceof Boolean) {
            return (Boolean) value ? "1" : "0";
        } else if (value instanceof Number) {
            return value.toString();
        } else {
            return value.toString();
        }
    }

    private String handleMapParameter(String sql, Map<?, ?> paramMap, List<ParameterMapping> parameterMappingList) {
        for (ParameterMapping parameterMapping : parameterMappingList) {
            String propertyName = parameterMapping.getProperty();
            Object propertyValue = getNestedParamValue(paramMap, propertyName);
            if (propertyValue == null) {
                logger.warn("Parameter '{}' not found in parameter map. Available parameters: {}", propertyName, paramMap.keySet());
                continue;
            }
            String formattedValue = formatParameterValue(propertyValue);
            logger.debug("Parameter name: {}, value: {}, type: {}", propertyName, formattedValue, propertyValue.getClass().getSimpleName());
            sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(formattedValue));
        }
        return sql;
    }
    private Object getNestedParamValue(Map<?, ?> paramMap, String propertyName) {
        if (propertyName.contains(".")) {
            String[] nestedProperties = propertyName.split("\\.");
            Object value = paramMap;
            for (String prop : nestedProperties) {
                if (value instanceof Map) {
                    value = ((Map<?, ?>) value).get(prop);
                } else {
                    // 如果不是Map，尝试通过反射获取属性值
                    try {
                        Field field = value.getClass().getDeclaredField(prop);
                        field.setAccessible(true);
                        value = field.get(value);
                    } catch (NoSuchFieldException | IllegalAccessException e) {
                        logger.error("Error accessing nested property '{}' on '{}'", prop, value.getClass().getName(), e);
                        return null;
                    }
                }
                if (value == null) {
                    return null;
                }
            }
            return value;
        } else {
            return paramMap.get(propertyName);
        }
    }

    private String handleListParameter(String sql, Collection<?> col) {
        if (col != null && !col.isEmpty()) {
            for (Object obj : col) {
                String value = obj.toString();
                if (obj.getClass().isAssignableFrom(String.class)) {
                    value = "\"" + value + "\"";
                }
                sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(value));
            }
        }
        return sql;
    }

    private String beautifySql(String sql) {
        // Remove excess whitespace
        sql = sql.replaceAll("[\\s\n]+", " ").trim();

        // Format common SQL keywords and clauses
        sql = sql.replaceAll("(?i)\\b(SELECT|INSERT INTO|UPDATE|DELETE FROM|FROM|WHERE|SET|VALUES|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|GROUP BY|ORDER BY|HAVING|LIMIT|OFFSET|UNION|UNION ALL|JOIN|ON|USING|DISTINCT|EXISTS|IN|IS NULL|IS NOT NULL|NOT IN|LIKE|BETWEEN|AND|OR|CASE|WHEN|THEN|ELSE|END)\\b", "\n$1");

        // Handle INSERT INTO and VALUES statements and parenthesis formatting
        sql = sql.replaceAll("(?i)\\b(INSERT INTO [^\\(]+\\()", "\n$1\n  ");
        sql = sql.replaceAll("(?i)\\b(VALUES)\\s*\\(", "\n$1\n  (");
        sql = sql.replaceFirst("\\)\\s*VALUES", "\n)\nVALUES");

        // Handle subqueries and nested queries
        sql = sql.replaceAll("(?i)\\b(SELECT)\\b", "\n$1");
        sql = sql.replaceAll("(?i)\\b(FROM)\\b", "\n$1");
        sql = sql.replaceAll("(?i)\\b(WHERE)\\b", "\n$1");
        sql = sql.replaceAll("(?i)\\b(GROUP BY)\\b", "\n$1");
        sql = sql.replaceAll("(?i)\\b(ORDER BY)\\b", "\n$1");

        // Handle indentation for subqueries
        sql = sql.replaceAll("\\(\\s*SELECT", "(\n  SELECT");
        sql = sql.replaceAll("\\)\\s*\\b", ")");

        // Split the SQL into lines and format
        String[] lines = sql.split("\n");
        StringBuilder formattedSql = new StringBuilder();
        int indentLevel = 0;

        for (String line : lines) {
            if (line.matches("(?i)^\\s*(SELECT|INSERT INTO|UPDATE|DELETE FROM|FROM|WHERE|SET|VALUES|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|GROUP BY|ORDER BY|HAVING|LIMIT|OFFSET|UNION|UNION ALL|JOIN|ON|USING|DISTINCT|EXISTS|IN|IS NULL|IS NOT NULL|NOT IN|LIKE|BETWEEN|AND|OR|CASE|WHEN|THEN|ELSE|END)\\b.*")) {
                if (line.matches("(?i)^\\s*(FROM|WHERE|SET|VALUES|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|GROUP BY|ORDER BY|HAVING|LIMIT|OFFSET|UNION|UNION ALL|JOIN|ON|USING|DISTINCT|EXISTS|IN|IS NULL|IS NOT NULL|NOT IN|LIKE|BETWEEN|AND|OR|CASE|WHEN|THEN|ELSE|END)\\b.*")) {
                    indentLevel--;
                }
                formattedSql.append(repeat("  ", indentLevel)).append(line.trim()).append("\n");
                if (line.matches("(?i)^\\s*(SELECT|INSERT INTO|UPDATE|DELETE FROM)\\b.*")) {
                    indentLevel++;
                }
            } else {
                formattedSql.append(repeat("  ", indentLevel)).append(line.trim()).append("\n");
            }
        }

        // Special handling for closing parenthesis
        formattedSql = new StringBuilder(formattedSql.toString().replace(") VALUES", ") VALUES"));
        formattedSql = new StringBuilder(formattedSql.toString().replaceAll("\\),", "),"));

        // Remove trailing spaces and tabs
        return formattedSql.toString().replaceAll("\n{2,}", "\n").trim();
    }

    private String repeat(String str, int count) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < count; i++) {
            result.append(str);
        }
        return result.toString();
    }

    private boolean isPrimitiveOrPrimitiveWrapper(Class<?> clazz) {
        return clazz.isPrimitive() || PRIMITIVE_WRAPPER_CLASSES.contains(clazz);
    }

    private boolean isStrictMap(Class<?> clazz) {
        return StrictMap.class.isAssignableFrom(clazz);
    }

    private boolean isList(Class<?> clazz) {
        return List.class.isAssignableFrom(clazz);
    }

    private boolean isMap(Class<?> clazz) {
        return Map.class.isAssignableFrom(clazz);
    }
}
